/*

 * Copyright (c) 2014, Oracle America, Inc.

 * All rights reserved.

 *

 * Redistribution and use in source and binary forms, with or without

 * modification, are permitted provided that the following conditions are met:

 *

 *  * Redistributions of source code must retain the above copyright notice,

 *    this list of conditions and the following disclaimer.

 *

 *  * Redistributions in binary form must reproduce the above copyright

 *    notice, this list of conditions and the following disclaimer in the

 *    documentation and/or other materials provided with the distribution.

 *

 *  * Neither the name of Oracle nor the names of its contributors may be used

 *    to endorse or promote products derived from this software without

 *    specific prior written permission.

 *

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"

 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE

 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE

 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR

 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF

 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN

 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)

 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF

 * THE POSSIBILITY OF SUCH DAMAGE.

 */

package org.openjdk.jmh.samples;


import org.openjdk.jmh.annotations.Benchmark;

import org.openjdk.jmh.annotations.BenchmarkMode;

import org.openjdk.jmh.annotations.CompilerControl;

import org.openjdk.jmh.annotations.Fork;

import org.openjdk.jmh.annotations.Measurement;

import org.openjdk.jmh.annotations.Mode;

import org.openjdk.jmh.annotations.OutputTimeUnit;

import org.openjdk.jmh.annotations.Param;

import org.openjdk.jmh.annotations.Scope;

import org.openjdk.jmh.annotations.Setup;

import org.openjdk.jmh.annotations.State;

import org.openjdk.jmh.annotations.Warmup;

import org.openjdk.jmh.infra.Blackhole;

import org.openjdk.jmh.runner.Runner;

import org.openjdk.jmh.runner.RunnerException;

import org.openjdk.jmh.runner.options.Options;

import org.openjdk.jmh.runner.options.OptionsBuilder;


import java.util.concurrent.TimeUnit;


@State(Scope.Thread)

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)

@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)

@Fork(3)

@BenchmarkMode(Mode.AverageTime)

@OutputTimeUnit(TimeUnit.NANOSECONDS)

public class JMHSample_34_SafeLooping {


 /*

  * JMHSample_11_Loops warns about the dangers of using loops in @Benchmark methods.

  * Sometimes, however, one needs to traverse through several elements in a dataset.

  * This is hard to do without loops, and therefore we need to devise a scheme for

  * safe looping.

  */


 /*

  * Suppose we want to measure how much it takes to execute work() with different

  * arguments. This mimics a frequent use case when multiple instances with the same

  * implementation, but different data, is measured.

  */


 static final int BASE = 42;


 static int work(int x) {

  return BASE + x;

 }


 /*

  * Every benchmark requires control. We do a trivial control for our benchmarks

  * by checking the benchmark costs are growing linearly with increased task size.

  * If it doesn't, then something wrong is happening.

  */


 @Param({"1", "10", "100", "1000"})

 int size;


 int[] xs;


 @Setup

 public void setup() {

  xs = new int[size];

  for (int c = 0; c < size; c++) {

   xs[c] = c;

  }

 }


 /*

  * First, the obviously wrong way: "saving" the result into a local variable would not

  * work. A sufficiently smart compiler will inline work(), and figure out only the last

  * work() call needs to be evaluated. Indeed, if you run it with varying $size, the score

  * will stay the same!

  */


 @Benchmark

 public int measureWrong_1() {

  int acc = 0;

  for (int x : xs) {

   acc = work(x);

  }

  return acc;

 }


 /*

  * Second, another wrong way: "accumulating" the result into a local variable. While

  * it would force the computation of each work() method, there are software pipelining

  * effects in action, that can merge the operations between two otherwise distinct work()

  * bodies. This will obliterate the benchmark setup.

  *

  * In this example, HotSpot does the unrolled loop, merges the $BASE operands into a single

  * addition to $acc, and then does a bunch of very tight stores of $x-s. The final performance

  * depends on how much of the loop unrolling happened *and* how much data is available to make

  * the large strides.

  */


 @Benchmark

 public int measureWrong_2() {

  int acc = 0;

  for (int x : xs) {

   acc += work(x);

  }

  return acc;

 }


 /*

  * Now, let's see how to measure these things properly. A very straight-forward way to

  * break the merging is to sink each result to Blackhole. This will force runtime to compute

  * every work() call in full. (We would normally like to care about several concurrent work()

  * computations at once, but the memory effects from Blackhole.consume() prevent those optimization

  * on most runtimes).

  */


 @Benchmark

 public void measureRight_1(Blackhole bh) {

  for (int x : xs) {

   bh.consume(work(x));

  }

 }


 /*

  * DANGEROUS AREA, PLEASE READ THE DESCRIPTION BELOW.

  *

  * Sometimes, the cost of sinking the value into a Blackhole is dominating the nano-benchmark score.

  * In these cases, one may try to do a make-shift "sinker" with non-inlineable method. This trick is

  * *very* VM-specific, and can only be used if you are verifying the generated code (that's a good

  * strategy when dealing with nano-benchmarks anyway).

  *

  * You SHOULD NOT use this trick in most cases. Apply only where needed.

  */


 @Benchmark

 public void measureRight_2() {

  for (int x : xs) {

   sink(work(x));

  }

 }


 @CompilerControl(CompilerControl.Mode.DONT_INLINE)

 public static void sink(int v) {

  // IT IS VERY IMPORTANT TO MATCH THE SIGNATURE TO AVOID AUTOBOXING.

  // The method intentionally does nothing.

 }



 /*

  * ============================== HOW TO RUN THIS TEST: ====================================

  *

  * You might notice measureWrong_1 does not depend on $size, measureWrong_2 has troubles with

  * linearity, and otherwise much faster than both measureRight_*. You can also see measureRight_2

  * is marginally faster than measureRight_1.

  *

  * You can run this test:

  *

  * a) Via the command line:

  *    $ mvn clean install

  *    $ java -jar target/benchmarks.jar JMHSample_34

  *

  * b) Via the Java API:

  *    (see the JMH homepage for possible caveats when running from IDE:

  *      http://openjdk.java.net/projects/code-tools/jmh/)

  */


 public static void main(String[] args) throws RunnerException {

  Options opt = new OptionsBuilder()

          .include(JMHSample_34_SafeLooping.class.getSimpleName())

          .forks(3)

          .build();


  new Runner(opt).run();

 }
// 这节专门讲如何构造安全的循环，和前面的内容稍有重复

}